package com.nebula.common.datasource.mybatis.spring;


import com.alibaba.druid.pool.DruidDataSourceFactory;

import com.nebula.common.datasource.mybatis.db.ds.DataSourceSupport;
import com.nebula.common.datasource.mybatis.db.ds.DynamicDataSource;
import com.nebula.common.datasource.mybatis.db.ShardDsNode;
import com.nebula.common.util.PropertyUtil;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.*;


/**
 * springboot集成mybatis的基本入口
 * 1）创建数据源(如果采用的是默认的tomcat-jdbc数据源，则不需要)
 * 2）创建SqlSessionFactory
 * 3）配置事务管理器，除非需要使用事务，否则不用配置
 */
//@Configuration // 该注解类似于spring配置文件
public abstract class MyBatisConfig {
    @Autowired
    private Environment env;
    //private ShardConfig aDefault;
    public static List<ShardDsNode> shardDsNodes=new ArrayList<>();
    private List<String> shardDsNames=new ArrayList<>();
    private List<String> dsNames=new ArrayList<>();

    /**
     * 初始化数据源
     */
    protected abstract void  initDs();

    /**
     * 添加shard数据源
     * @param shardDsName
     */
    protected void addShardDsName(String shardDsName){
        shardDsNames.add(shardDsName);
    }

    /**
     * 添加普通数据源
     * @param dsName
     */
    protected void addDsName(String dsName){
        dsNames.add(dsName);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候，默认选择哪一个，而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入，通常是在具有相同的多个类型的实例的一个注入（例如有多个DataSource类型的实例）
     */
    @Bean(name="dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource() {
        initDs();//初始化数据源

        Map<Object, Object> targetDataSourcesMap = new HashMap<>();
        DataSource defaultDs=null;

        for (String shardDsName:shardDsNames) {
            this.addShardDs(targetDataSourcesMap,shardDsName);
        }
        for (String dsName:dsNames) {
            this.addDs(targetDataSourcesMap,dsName);
        }

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSourcesMap);// 该方法是AbstractRoutingDataSource的方法

        //设置默认数据源
        for (Map.Entry<Object, Object> entry : targetDataSourcesMap.entrySet()) {
            defaultDs= (DataSource)entry.getValue();
            break;
        }
        dataSource.setDefaultTargetDataSource(defaultDs);

        return dataSource;
    }

    //添加普通数据源
    private   void addDs(Map<Object, Object> targetDataSourcesMap,String dsName){
        DataSource ds=this.initDataSource(dsName);
        targetDataSourcesMap.put(dsName,ds);
    }

    //添加shard数据源
    private void addShardDs(Map<Object, Object> targetDataSourcesMap,String shardName){
       /* ShardConfig.ShardInfo info= this.getNodeFromRemote(shardName);
        List<ShardConfig.NodeInfo>  list=info.getNodes();
        for (ShardConfig.NodeInfo nodeInfo:list) {
            long start=StringConverter.toInt64(nodeInfo.getArgs().get("start"));
            long end=StringConverter.toInt64(nodeInfo.getArgs().get("end"));
            int mod=StringConverter.toInt32(nodeInfo.getArgs().get("mod"));
            for (ShardConfig.NodeInfo sinfo:nodeInfo.getNodes()) {
                int remainders=StringConverter.toInt32(sinfo.getArgs().get("remainders"));

                String writeName=sinfo.getArgs().get("write");
                DataSource writeDs=this.initDataSource(writeName);
                ShardDsNode ShardDsNode=new ShardDsNode(shardName,start,end,mod,remainders,"write");
                shardDsNodes.add(ShardDsNode);
                targetDataSourcesMap.put(ShardDsNode.getDsKey(), writeDs);

                String readName=sinfo.getArgs().get("read");
                DataSource readDs=this.initDataSource(readName);
                ShardDsNode=new ShardDsNode(shardName,start,end,mod,remainders,"read");
                shardDsNodes.add(ShardDsNode);
                targetDataSourcesMap.put(ShardDsNode.getDsKey(), readDs);
            }
        }*/

       //临时方案
        String writeName="writeDs";
        DataSource writeDs=this.initDataSource(writeName);
        ShardDsNode ShardDsNode=new ShardDsNode(shardName,1,1000000,2,0,"write");
        shardDsNodes.add(ShardDsNode);
        targetDataSourcesMap.put(ShardDsNode.getDsKey(), writeDs);

        String readName="readDs";
        DataSource readDs=this.initDataSource(readName);
        ShardDsNode=new ShardDsNode(shardName,1,1000000,2,1,"read");
        shardDsNodes.add(ShardDsNode);
        targetDataSourcesMap.put(ShardDsNode.getDsKey(), readDs);

    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(dynamicDataSource);// 指定数据源(这个必须有，否则报错)
        // 下边两句仅仅用于*.xml文件，如果整个持久层操作不需要使用到xml文件的话（只用注解就可以搞定），则不加
        //fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
        //fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
        //fb.setConfigLocation(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.configLocation"))[0]);

        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));
        fb.setConfigLocation(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis.xml")[0]);
        return fb.getObject();
    }
    
    @Bean(name="dataSourceSupport")
    public DataSourceSupport dataSourceSupport(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory){
    	DataSourceSupport dss=new DataSourceSupport();
    	dss.setSqlSessionFactory(sqlSessionFactory);
    	return dss;
    }
    
    /**
     * 配置事务管理器
     */
    @Bean(name="txManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception {
        return new DataSourceTransactionManager(dynamicDataSource);
    }

    /**
     * 从配置中心获取shard配置
     * @param name
     * @return
     */
    /*private  ShardConfig.ShardInfo getNodeFromRemote(String name) {
        Map<String, String> m = new HashMap<>();
        m.put("name", name);
        RemoteLoader.Result result = RemoteLoader.load("/db/shard", m);
        if (!result.isSuccess()) {
            return null;
        }
        ShardConfig.ShardInfo shardInfo = JsonEncoder.DEFAULT.decode(result.getValue(), ShardConfig.ShardInfo.class);
        return  shardInfo;
    }*/

    /**
     * 初始化数据源
     * @param dsName
     * @return
     */
    private DataSource initDataSource(String dsName){
        DataSource ds=null;
       /* DatabaseConfig.SqlDBInfo info=DatabaseConfig.get(dsName);
        Properties props = new Properties();
        String driverClass="";
        if (info.getType().equals("mysql")){
            driverClass="com.mysql.jdbc.Driver";
        }else {
            driverClass="com.microsoft.sqlserver.jdbc.SQLServerDriver";
        }
        props.put("driverClassName",driverClass);
        props.put("url", info.getUrl());
        props.put("username",info.getUser());
        props.put("password", info.getPwd());
        try {
            ds=  DruidDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }*/

        //临时
        Properties props = new Properties();
        String driverClass="com.mysql.jdbc.Driver";
        props.put("driverClassName",driverClass);
        props.put("url", PropertyUtil.getProperty("jdbc.url"));
        props.put("username",PropertyUtil.getProperty("jdbc.username"));
        props.put("password", PropertyUtil.getProperty("jdbc.password"));
        try {
            ds=  DruidDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  ds;
    }

}
